package gecko.impl;

import gecko.AbstractDevice;
import gecko.EventFrame;
import gecko.GeckoScoped;
import gecko.lang.JSONObject;
import gecko.lang.TypedMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.net.*;
import java.util.concurrent.CompletableFuture;


/**
 * 支持UDP通讯的设备。
 * 在此处，UDP设备作为客户端，另一方作为服务端，接收此Device的消息，并返回响应。
 * 使用UDP设备时，需要指定：
 * - GroupAddress：目标UDP通讯设备的IP地址；
 * - PhysicalAddress: 目标UDP通讯设备的端口；
 *
 * @author 陈永佳 (yoojiachen@gmail.com)
 * @version 0.0.1
 */
public class UdpVirtualDevice extends AbstractDevice {

    private static final Logger LOGGER = LoggerFactory.getLogger(UdpVirtualDevice.class);

    public static final String PROTOCOL = "udp";

    private byte[] mReadBuffer;
    private DatagramSocket mUDPSocket;
    private InetSocketAddress mRemoteAddress;

    @Override
    public void onInit(TypedMap initArgs, GeckoScoped scoped) {
        super.onInit(initArgs, scoped);
        mReadBuffer = new byte[initArgs.getInt("byteBuffKB", 1) * 1024];
        mRemoteAddress = InetSocketAddress.createUnresolved(getDefineUdpAddress(), getDefineUdpPort());
        LOGGER.info("初始化UDP虚拟设备客户端: {}", mRemoteAddress);
    }

    @Override
    public CompletableFuture<EventFrame> process(EventFrame event, GeckoScoped scoped) throws Exception {
        return scoped.submitFuture(() -> {
            scoped.DebugIfV(LOGGER, "UDP虚拟设备客户端处理指令: {}", getRemoteAddress());
            // Send
            try {
                udpSend(event.bytes, getRemoteAddress(), getRemotePort());
            } catch (Exception ex) {
                return event.newOf(JSONObject.error("UDP_SEND_ERR:" + ex.getMessage()).toJSONBytes());
            }
            // Recv
            try {
                final DatagramPacket receive = udpRecv();
                return event.newOf(receive.getData());
            } catch (Exception ex) {
                return event.newOf(JSONObject.error("UDP_RECV_ERR:" + ex.getMessage()).toJSONBytes());
            }
        });
    }

    @Override
    public void onStart(GeckoScoped scoped) {
        super.onStart(scoped);
        try {
            final TypedMap args = getInitArgs();
            mUDPSocket = new DatagramSocket();
            mUDPSocket.setReceiveBufferSize(args.getInt("recvBuffSizeKB", 1) * 1024);
            mUDPSocket.setSendBufferSize(args.getInt("sendBuffSizeKB", 1) * 1024);
            mUDPSocket.setSoTimeout(args.getInt("soTimeoutSec", 3) * 1000);
        } catch (SocketException ex) {
            LOGGER.error("无法创建UDP客户端", ex);
            throw new RuntimeException(ex);
        }
    }

    @Override
    public void onStop(GeckoScoped scoped) {
        super.onStop(scoped);
        if (mUDPSocket != null) {
            mUDPSocket.close();
        }
    }

    /**
     * 发送UDP数据包
     *
     * @param bytes   数据
     * @param address 目标地址
     * @throws IOException IO异常
     */
    protected void udpSend(byte[] bytes, InetAddress address, int port) throws IOException {
        final DatagramPacket send = new DatagramPacket(bytes, bytes.length, address, port);
        mUDPSocket.send(send);
    }

    /**
     * 发送UDP数据包到当前目标设备
     *
     * @param bytes 数据
     * @throws IOException IO异常
     */
    protected void udpSend(byte[] bytes) throws IOException {
        udpSend(bytes, getRemoteAddress(), getRemotePort());
    }

    /**
     * 读取UDP数据包；读取超时已通过 initArgs.soTimeout 设置。
     *
     * @return DatagramPacket
     * @throws IOException IO异常
     */
    protected DatagramPacket udpRecv() throws IOException {
        DatagramPacket receive = new DatagramPacket(mReadBuffer, mReadBuffer.length);
        mUDPSocket.receive(receive);
        return receive;
    }

    /**
     * @return 返回UDP通讯地址，默认情况下，是 GroupAddress。
     */
    protected String getDefineUdpAddress() {
        return getGroupAddress();
    }

    /**
     * @return 返回UDP通讯地址端口，默认情况下，是 PhysicalAddress。
     */
    protected int getDefineUdpPort() {
        return Integer.parseInt(getPhysicalAddress());
    }

    /**
     * @return 返回当前设备配置的远程地址
     */
    protected InetAddress getRemoteAddress() {
        return mRemoteAddress.getAddress();
    }

    /**
     * @return 返回当前设备配置的远程端口
     */
    protected int getRemotePort() {
        return mRemoteAddress.getPort();
    }

    @Override
    public String getProtocolName() {
        return PROTOCOL;
    }

}